package cn.xw.utils.jwtTools;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.util.*;

/**
 * @author Anhui OuYang
 * @version 1.0
 **/
public class JwtHSUtils {

    private static final Logger log = LoggerFactory.getLogger(JwtHSUtils.class);

    // 密钥信息,必须为32位（切忌不可暴露）
    private static final byte[] secretKey = "qW3e#rT5tY7u*I9oP1aS2dF4gH6jK8lZ0".getBytes();
    // 签名算法（这里可以设置HS256、HS384、HS512；值越大的安全性越高，但是性能差）
    private static final JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;

    // JWT载荷主要信息
    private static final String CLAIM = "claim";
    // 载荷信息过期时间(分钟)
    private static final Integer payloadExpTime = 60 * 24;

    /***
     * 生成 JWT Token 信息（采用非对称加密方式，一个密钥进行加密或解密）
     * @param payloadInfo JWT的载荷信息
     * @return token信息
     */
    public static String generateJwtToken(PayloadInfo<?> payloadInfo) {
        // 载荷信息处理（设置过期时间、生效时间、发布时间）
        Calendar calendar = Calendar.getInstance();
        Date currentTime = calendar.getTime();
        calendar.add(Calendar.MINUTE, payloadExpTime);
        payloadInfo.setExp(calendar.getTime());
        payloadInfo.setNbf(currentTime);
        payloadInfo.setIat(currentTime);
        try {
            // 创建JWS头，设置签名算法和类型(JWT、JOSE、JOSE_JSON)
            JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).type(JOSEObjectType.JWT).build();
            // 设置载荷7种基本信息，以及一种主要载荷信息
            JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                    .issuer(payloadInfo.getIss()).subject(payloadInfo.getSub()).audience(payloadInfo.getAud())
                    .expirationTime(payloadInfo.getExp()).notBeforeTime(payloadInfo.getNbf())
                    .issueTime(payloadInfo.getIat()).jwtID(payloadInfo.getJti())
                    .claim(CLAIM, payloadInfo.getClaim()).build();
            // JWSObject对象将JWT头和JWT Payload组合起来，形成一个待签名的JWT Token。
            JWSObject jwsObject = new SignedJWT(jwsHeader, claimsSet);
            // 基于HMAC实现的数字签名算法，适用于共享密钥的对称密钥加密场景，提供一个共享密钥
            JWSSigner jwsSigner = new MACSigner(secretKey);
            // 对数据进行签名，生产第三部分签名信息
            jwsObject.sign(jwsSigner);
            // 返回完整Token信息（xxx.xxx.xxx）
            return jwsObject.serialize();
        } catch (JOSEException e) {
            log.info("Token签名失败，可能原因：参数不合法、算法不支持、" +
                    "签名验证失败、加密解密错误等；响应错误信息：{}", e.getMessage());
        }
        return "";
    }

    /***
     * 解析 JWT Token 信息（采用非对称加密方式，一个密钥进行加密或解密）
     * @param jwtToken 已经存在的token字符串
     * @return 解析后的载荷信息，（若解析失败则返回null）
     */
    public static <T> PayloadInfo<T> analysisJwtToken(String jwtToken, Class<T> clazz) {

        // 初始化返回对象
        PayloadInfo<T> payloadInfo = null;
        // 初始化对象
        JWSObject jwsObject = null;
        // jwtToken处理，有些Token会携带前缀 “Bearer ”
       if(jwtToken.startsWith("Bearer")){
           //包含则剔除
           jwtToken = jwtToken.substring(6).trim();
       }
        try {
            jwsObject = SignedJWT.parse(jwtToken);
        } catch (ParseException e) {
            log.info("token字符串无法被正常解析：{}", e.getMessage());
            throw new RuntimeException("token字符串无法被正常解析：" + e.getMessage());
        }

        // 校验token头部信息
        if (!isValidJwtHeader(jwsObject)) {
            return null;
        }
        // 校验载荷信息当前是否合法（验签，数据是否被非法篡改）
        if (!isValidToken(jwsObject)) {
            return null;
        }

        // 载荷信息解析（就是我们之前的PayloadInfo<T>里的信息）
        Map<String, Object> jsonObjectMap = jwsObject.getPayload().toJSONObject();
        JWTClaimsSet parse = null;
        try {
            parse = JWTClaimsSet.parse(jsonObjectMap);
        } catch (ParseException e) {
            log.info("token载荷信息解析失败：{}", e.getMessage());
            return null;
        }
        // 校验token是否过期（若不存在过期时间则一直有效）
        if (parse.getExpirationTime() != null) {
            if (!new Date().before(parse.getExpirationTime())) {
                log.info("token信息已过期。");
                return null;
            }
        }

        // 装载处理载荷信息，并返回指定信息
        payloadInfo = JSONObject.parseObject(JSONObject.toJSONString(jwsObject.getPayload().toJSONObject()),
                new TypeReference<PayloadInfo<T>>() {
                });
        T claim = JSONObject.parseObject(JSONObject.toJSONString(payloadInfo.getClaim()), clazz);
        payloadInfo.setClaim(claim);
        payloadInfo.setExp(parse.getExpirationTime());
        payloadInfo.setNbf(parse.getNotBeforeTime());
        payloadInfo.setIat(parse.getIssueTime());
        return payloadInfo;
    }

    /***
     * 校验 JWT Token 的头信息是否正确（无法校验其合法性）
     * @param jwsObject 已经被解析的token信息，
     * @return 是否为有效的头信息
     */
    public static boolean isValidJwtHeader(JWSObject jwsObject) {
        // 获取token头部信息
        JWSHeader header = jwsObject.getHeader();
        // 解析头部信息是否和之前生成的头部信息算法一样
        if (!header.getAlgorithm().equals(jwsAlgorithm)) {
            log.info("token头信息解析发生错误，和预先生成的加密算法不一样。");
            return false;
        }
        return true;
    }

    /***
     * 校验 JWT Token 的信息是否合法（验签）
     * @param jwsObject 已经被解析的token信息，
     * @return 是否为有效的信息（true代表token信息未被非法篡改）
     */
    public static boolean isValidToken(JWSObject jwsObject) {
        try {
            // 创建一个验签类，并设置密钥信息
            JWSVerifier jwsVerifier = new MACVerifier(secretKey);
            // 校验信息是否被篡改
            boolean verify = jwsObject.verify(jwsVerifier);
            if (verify)
                return true;
            log.info("token验签失败，存在非法篡改token信息。");
        } catch (JOSEException e) {
            log.info("token验签失败：{}", e.getMessage());
        }
        return false;
    }
}
